import ferp.center.network.RequestBidAdd;
import ferp.center.network.ResponseBidDataGet;
import ferp.core.ai.nn.Adaptation;
import ferp.core.ai.nn.bp.BPNN;
import ferp.core.ai.nn.bp.activation.Activation;
import ferp.core.ai.nn.bp.activation.Sigmoid;
import ferp.core.ai.nn.bp.training.Backprop;
import ferp.core.game.Game;

import java.io.*;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;

/**
 * User: igorgok
 * Date: 16/07/2014 16:38
 */
public class Trainer
{
  public static void main(String[] args) throws IOException
  {
    //download();
    train();
  }

  private static void train() throws IOException
  {
    // prepare the training set
    System.out.println("prepairing training set...");

    long start = System.currentTimeMillis();
    List<RequestBidAdd> bids = getAllBids();
    double[][] set = new double[bids.size()][];
    int row = 0;

    for (RequestBidAdd b : bids)
      set[row++] = Adaptation.bid2nnt(b.cards, b.hand, b.starting, b.last, b.penalty, b.result);

    long prepared = System.currentTimeMillis();

    // train
    BPNN bpnn = train(set);
    long trained = System.currentTimeMillis();

    // test
    System.out.println("testing the original NN...");

    int success = test(bpnn, set);
    long tested = System.currentTimeMillis();

    // serialize and save to file
    System.out.println("serializing NN...");

    String json = BPNN.gson.toJson(bpnn);
    PrintWriter pw = new PrintWriter("nn." + bpnn.getHiddenLayersCount() + "." + bpnn.getLayerSize(1) + ".json");

    pw.print(json);
    pw.close();

    long serialized = System.currentTimeMillis();

    // loading serialized NN and testing again
    System.out.println("loading serialized NN and testing it...");
    BPNN bpnn2 = BPNN.gson.fromJson(json, BPNN.class);
    int success2 = test(bpnn2, set);

    System.out.println("Training set size: " + set.length);
    System.out.println("Orig. NN test error rate: " + ((set.length - success) * 100 / set.length) + "%");
    System.out.println("Rest. NN test error rate: " + ((set.length - success2) * 100 / set.length) + "%");
    System.out.println("Saved network state size: json=" + json.length());
    System.out.println("Times: prepare=" + (prepared - start) + "ms, train=" + (trained - prepared) / 1000 +
        "s, test=" + (tested - trained) + "ms, serialize=" + (serialized - tested) + "ms");
  }
  
  private static List<RequestBidAdd> getAllBids() throws IOException
  {
    BufferedReader br = new BufferedReader(new FileReader(FILE));
    List<RequestBidAdd> bids = new LinkedList<RequestBidAdd>();

    for (String s = br.readLine(); s != null; s = br.readLine())
    {
      ResponseBidDataGet rbdg = Game.gson.fromJson(s, ResponseBidDataGet.class);

      bids.addAll(rbdg.bids);
    }
    
    br.close();
    
    return bids;
  }

  private static int test(BPNN bpnn, double[][] set)
  {
    int success = 0;

    for (double[] r : set)
    {
      // save the original result
      double result = r[r.length - 1];
      // calculate NN result
      double[] calculated = bpnn.process(r);

      calculated[0] = calculated[0] < 0.5 ? 0 : 1;

      if (calculated[0] == result)
        ++success;
    }

    return success;
  }

  private static BPNN train(double[][] set)
  {
    int os = 1, is = set[0].length - os;
    int hls = is / 2;
    BPNN bpnn = new BPNN(new int[]{set[0].length - 1, 72, 1}, new Activation[]{Sigmoid.instance, Sigmoid.instance});
    Backprop bp = new Backprop(bpnn);

    // train
    System.out.println("training the NN...");

    long epochs = bp.train(set, 0.2, 0.9, 0.1);

    return bpnn;
  }

  private static void download() throws IOException
  {
    PrintWriter pw = new PrintWriter(FILE);

    try
    {
      for (int i = 0; i < 60000 / CHUNK_SIZE; ++i)
      {
        int start = CHUNK_SIZE * i;

        System.out.println("fetching chunk #" + i);

        URL url = new URL("https://ferpcenter.appspot.com/bid/data/get?s=" + start + "&c=" + CHUNK_SIZE);
        BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
        String line = br.readLine();
        ResponseBidDataGet rbdg = Game.gson.fromJson(line, ResponseBidDataGet.class);

        System.out.println("  got " + rbdg.bids.size() + " records");

        if (rbdg.bids.size() == 0)
          break;

        pw.println(line);
      }
    }
    finally
    {
      pw.close();
    }
  }

  private static final String FILE = "bids.txt";
  private static final int CHUNK_SIZE = 500;
}
